home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
C/C++ Users Group Library 1996 July
/
C-C++ Users Group Library July 1996.iso
/
vol_200
/
266_01
/
update.txt
< prev
next >
Wrap
Text File
|
1989-04-19
|
12KB
|
287 lines
**** This text is extracted from the volume 7.4. ****
Porting Microplox To XENIX
Microplox (CUG266) is a graph drawing program that works under CP/M
or MS-DOS using an Epson FX-80 printer (or in our case, a laser printer
which can emulate the Epson). Recently we adapted the MS-DOS original
to work under XENIX. In a technical sense the incompatibilities were
relatively mild and commonplace (BIOS I/O calls had to be mapped to
XENIX I/O capabilities and int/pointer puns had to be eliminated),
but locating and understanding these problems was somewhat complicated
by Microplox's unusual coding and design style.
The Implementation
Microplox consists of two programs:plox translates a graph
specification into a file of reasonably abstract plotting commands
(PLOXCOM.DAT) and ploteps builds and prints an Epson
compatible bit map from the plotting commands.
ploteps is conventional in design and coding, but plox
is (at least at the top level) an unusual collection of object-like
functions, one for each different type of statement in the Microplox
specification grammar. These object-like functions have uniform message-passing
interfaces which require a single text string (messages) as input. Each
message consists of a series of keyword-value pairs. The function
parses the keyword to select an appropriate method for processing
the associated value. Thus a specification file can be parsed merely
by repackaging each statement as a message and sending it to the appropriate
object function.
Each of these object functions also owns certain private (static)
data. Low level messages (those with strictly local effect) result
either in changes to this private data or immediate output of a plotting
command or both. Higher level messages (those that affect non-local
data), however, may require changes to the private data of several
distinct object functions. Object functions effect changes in the
private data of other object functions by constructing a message and
sending it via the function SendSpec().
SendSpec() requires three arguments: a string representing
the method part of the message, a value, and a pointer to the target
object function. SendSpec() formats the value as a string,
appends it to the method and invokes (via the pointer) the target
function with the resulting message as the single input parameter
(see Listing 1).
/* Send a "keyword value" string to a given function */
void SendSpec (KeyWord, Value, Process)
char *KeyWord;
int Value, (*Process)();
{
char Digits[7], Spec[20];
strcpy (Spec, KeyWord);
strcat (Spec, " ");
sprintf (Digits, "%d", Value);
strcat (Spec, Digits);
(*Process) (Spec);
}
(Listing 1)
The second paramter, Value, is declared as a simple int. Unfortunately,
not all methods require a simple int as their value. Some
require strings, others need several values. In classic C tradition,
SendSpec() ignores these differences, assuming that int
is actually a universal type. Both pointers and ints are passed
as values. SendSpec() converts each to an ASCII string,
as if an int had been received, and the receiving function
must examine the method and use the received value correctly.
This type pun works fine as long as ints and pointers
are the same size and pointers can be reliably converted to and from
an integer representation. But, when pointers are 32 bits and ints
16 (as in our target XENIX environment), the result is a program that
compiles without warning but immediately dumps core when executed.
We considered several alternative repairs. We could merely have lengthened
Value to 32 bits (cast ints to long at each call). With
appropriate typedefs and a few coding changes, we could have
made it easy for others to readjust the variable sizes to match other
machines. But what about environments where pointers don't have a
meaningful or unique integer representation? We decided there was
no certain way to sprintf() a pointer to a character string
and still be able to convert it back.
We also considered passing through a union, but that seemed to complicate
both the sending and receiving code at least as much as the obvious
alternative: adding a second field to the basic message.
The New Message
As originally coded, all plox messages were a single string,
possibly with multiple method pairs. We modified that so that messages
were a string and a possibly NULL pointer value (see Listing
2). Most methods are still paired with their values in the message
string, but methods requiring a pointer value retreive it from the
pointer parameter instead.
void SendSpec (KeyWord, Value, Scaleptr, Process)
char *KeyWord;
int Value, (*Process)();
SCALING *Scalepr;
{
char Digits[7], Spec[20];
strcpy (Spec, KeyWord);
strcat (Spec, " ");
if (Value != 0) {
sprintf (Digits, "%d", Value);
strcat (Spec, Digits);
}
(*Process) (Spec, Scaleptr);
}
(Listing 2)
We considered putting both parts of the message in a structure, but
that approach would have required significant changes throughout the
program in how variables are declared and used. Adding a parameter
required changes only to the calls to SendSpec(),the calling
interfaces of the object functions, and the few methods which expect
pointer values. Passing what is conceptually a single message as
two distinct parameters lacks elegance, but works and seems to do
less injury to the code than the alternatives.
The Printer Driver
ploteps is responsible for creating a graphic image and sending
it to the printer. In the orginal program, the image was output via
calls to BDOS functions a single-user approach not feasible
under XENIX. We considered two alternatives: replacing the low-level
MS-DOS I/O calls with comparable low-level XENIX/UNIX I/O calls, and
restructuring the I/O to take advantage of UNIX streams through high-level
buffered I/O commands.
The first method is straightforward. The program opens the printer
device file and writes the graphic image directly to the printer.
System functions open(), read(), write(), ioctl() and close()
will have to be used in the program. The second method is much more
flexible, easier to debug, and doesn't interfere with sharing the
printer among several users. Under the second method, the program
generates a binary temporary file that holds a graphic image. This
binary file is sent to the print spooler using the lp or lpr
commands (Figure 1).
------- --------- ----
| plox | --> PLOTCOM.DAT --> | ploteps | --> IMAGE ---> | lp |
------- --------- ----
|
V
---------------------
| printer | device |
(Figure 1) | interface | driver |
---------------------
Unfortunately the stock printer interface performs certain post-processing
on text files. At a minimum newlines (linefeeds) are converted to
CR-LF pairs. The binary file includes escape sequences to put the
printer in graphics mode so that it will interpret a binary 0x0A
as a dot pattern, but the interface and printer device driver don't
recognize the graphics mode, and continue to expand linefeeds.
To circumvent this problem, we modified the interface program (a piece
of shell script) by adding a local binary option.
Patching The Interface
Each printer device under UNIX/XENIX has its own printer interface
program at the subdirectory, /usr/spool/lp/interface. The printer
interface program is a piece of shell script that is invoked each
time lp is executed with the target printer. The task of the
interface program is to configure the device driver (via stty)
to properly handle the printer, displa